<?php
/**
 * Created by PhpStorm.
 * User: Administrator
 * Date: 2024/3/21
 * Time: 9:50
 */

namespace app\logic\index\user;


use app\common\lib\Common;
use app\common\lib\Constant;
use app\common\lib\Ffmpeg;
use app\common\lib\QRCodeImage;
use app\common\lib\RedisQueue;
use app\common\lib\TencentCos;
use app\common\lib\YidunQuickPass;
use app\exception\IndexException;
use app\logic\BaseLogic;
use app\model\consume\ConsumePointLogModel;
use app\model\novel\NovelChapterPartModel;
use app\model\novel\NovelSpreadModel;
use app\model\plan\BackgroundMusicModel;
use app\model\plan\NovelChapterPartOverseaModel;
use app\model\plan\PromotionPlanModel;
use app\model\plan\PromotionPlanOverseaModel;
use app\model\tool\ToolManageModel;
use app\model\tutorial\VideoTutorialModel;
use app\model\user\UserIncomeFlowModel;
use app\model\user\UserInviteRelationModel;
use app\model\user\UserLoginRecordModel;
use app\model\user\UserModel;
use app\model\user\UserNovelChapterPartModel;
use app\model\user\UserPointFlowModel;
use app\model\user\UserPromotionOverseaModel;
use app\model\user\UserPromotionOverseaRollingImgModel;
use app\model\user\UserPromotionPlanModel;
use app\model\user\UserRechargeOrderModel;
use app\model\vip\ReceiveVipByMobileModel;
use JamesHeinrich\GetID3\GetID3;
use support\Cache;
use support\Db;
use support\Log;

class UserLogic extends BaseLogic
{
    protected static $instance;

    public function __construct()
    {
        parent::__construct();
    }

    public static function getInstance()
    {
//        if(is_object(self::$instance)){
//            return self::$instance;
//        }

        return  new self();
    }

    public function guestLogin($param)
    {
        $uuid = request()->uuid;
        if($uuid==false){
            throw new IndexException(4020,'设备号不能空');
        }

        $user = UserModel::getInstance()->getWhere(['uuid'=>$uuid,['mobile','=',''],'isdel'=>0])->first();
        if($user){
            //该设备已存在游客账号直接切换即可
            $user = $this->getInfo($user);
            $user = $this->loginData($user);
        }else{

            //重新注册一个游客用户
            $user = $this->createUser(['uuid'=>$uuid]);
            $user = $this->loginData($user);

            //加入巨量引擎激活回传队列
            //生成唯一的请求id
            $requestId = Common::getRequestId();
            // 队列名
            $queue = 'oceanengineAttribution';
            // 数据，可以直接传数组，无需序列化
            $data = ['requestid' => $requestId, 'user_sn'=>$user['user_sn'],'device'=>request()->deviceNumber ,'type'=>'active' ,'platform'=>request()->platform];
            // 投递消息
            RedisQueue::getInstance()->sendQueue($queue, $data);
        }

        return $user;
    }

    public function createUser($param)
    {
        $param['mobile'] = $param['mobile']??'';
        $uuid = $param['uuid']??(request()->uuid??'');

        //生成唯一邀请码
        $inviteCode = Common::generateInviteCode(UserModel::getInstance(),'invit_code');

        //生成邀请二维码
        $qrcodeImage = QRCodeImage::getInstance()->generateUserQrCode(config('param.invit_qrcode_link').'?code='.$inviteCode);

        $mobileStr = substr($param['mobile'],-6);
        $mobileStr==false && $mobileStr = $inviteCode;
        $param['mobile'] && $param['nickname'] = ('推文_'.$mobileStr);
        $userData = [
            'mobile' => $param['mobile']??'',
            'nickname' => $param['nickname']??('匿名_'.$mobileStr),
            'scene' => $param['scene']??'',
            'parentid' => $param['parentid']??'0',
            'invit_code' => $inviteCode,
            'qrcode_image' => $qrcodeImage,
            'user_role' => $param['user_role']??0,
            'uuid' => $uuid,
            'weixin_unionid' => $param['weixin_unionid']??'',
            'ios_unionid' => $param['ios_unionid']??'',
            'google_unionid' => $param['google_unionid']??'',
        ];
        $userId = UserModel::getInstance()->insertData($userData);

        $user = UserModel::getInstance()->find($userId);

        //如果有邀请人，则更新邀请人数
        if(!empty($user['parentid'])){
            UserModel::getInstance()->updateInviteUserNums($user['user_sn'],$user['parentid'],1);
        }

        $user = $this->getInfo($user);

        //领取一天vip，是否领取然后进来注册，然后直接开通vip
        if(!empty($param['mobile'])){
            $receive = ReceiveVipByMobileModel::getInstance()->getWhere(['mobile'=>$param['mobile']])->first();
            if($receive){
                $this->receiveVipOpen($user,$receive['id']);
            }
        }

        //审核中的用户注册赠送200点数
//        if(Common::checkIngStatus()){
//            $insert = [
//                'user_sn'=>$user['user_sn'],
//                'nums'=>200,
//                'type'=>2,
//                'way'=>1,
//                'sourceid'=>$userId,
//                'source_table'=>UserModel::getInstance()->getTable(),
//            ];
//            UserPointFlowModel::getInstance()->insertGetId($insert);
//        }
        return $user;
    }

    public function getInfo($user=false)
    {
        if($user instanceof UserModel ==false){
            $user = UserModel::getInstance()->getWhere(['user_sn'=>request()->curUserId])->first();
        }

        //邀请二维码为空，就重新生成一次
        if(empty($user['qrcode_image']) && !empty($user['invit_code'])){
            $qrcodeImage = QRCodeImage::getInstance()->generateUserQrCode(config('param.invit_qrcode_link').'?code='.$user['invit_code']);
            UserModel::getInstance()->getWhere(['user_sn'=>$user['user_sn']])->update(['qrcode_image'=>$qrcodeImage]);
        }

        $isVip = $user['is_vip'];
        $user = $this->checkIsVip($user);

        //获取会员点数
        if($user['is_vip']==1){
            $where = [
                'user_sn'=>$user['user_sn'],
                'type'=>1,
                ['created_at','>=',date('Y-m-d 00:00:00')]
            ];
            $user['vip_point_nums'] = UserPointFlowModel::getInstance()->getWhere($where)->sum('nums');
        }elseif($user['is_vip']==2){
            $vipPointNums1 = (int) UserPointFlowModel::getInstance()->getWhere(['user_sn'=>request()->curUserId,'type'=>3,'way'=>2])->sum('nums');
            $vipPointNums2 = (int) UserPointFlowModel::getInstance()->getWhere(['user_sn'=>request()->curUserId,'type'=>3,'way'=>1, ['expired_time','>=',date('Y-m-d H:i:s')]])->sum('nums');
            $vipPointNums = round($vipPointNums2 - abs($vipPointNums1));
            $vipPointNums<0 && $vipPointNums = 0;
            $user['vip_point_nums'] = $vipPointNums;
        }else{
            $user['vip_point_nums'] = 0;
        }

        //获取购买点数
        $where = [
            'user_sn'=>$user['user_sn'],
            'type'=>2,
        ];
        $user['buy_point_nums'] = UserPointFlowModel::getInstance()->getWhere($where)->sum('nums');

        //每天赠送点数
        $everyDayGivePoint = 0;
        if($user['is_vip']>0){
            $where = [
                ['product_type', '=', 1],
                ['user_sn', '=', $user['user_sn']],
                ['order_status', '=', Constant::ORDER_STATUS_PAY_SUCCESS],
                ['pay_status', '=', Constant::ORDER_PAY_STATUS_SUCCESS],
                ['pay_time', '<>', ''],
                ['expires_date', '>=', strtotime(date('Y-m-d'))],
            ];
            $order = UserRechargeOrderModel::getInstance()->getWhere($where)->orderBy('point_nums','desc')->first();
            if($order) {
                $everyDayGivePoint = $order['point_nums'];
                $user['vip_expire_time'] = date('Y-m-d',strtotime($user['vip_expire_time']));
            }elseif($isVip==2){
                $everyDayGivePoint = config('param.test_vip_give_point',20);
                $user['vip_expire_time'] = date('m-d H:i',strtotime($user['vip_expire_time']));
            }
        }else{
            $user['vip_expire_time'] = '';
        }

        $user['every_day_give_point'] =  (string) $everyDayGivePoint;

        //邀请人数
        $user['invite_user_nums'] = UserInviteRelationModel::getInstance()->getWhere(['user_sn'=>request()->curUserId,'level'=>1])->count();
        $levelData = config('param.vip_level'.$user['agent_level']);
        $user['income_rate'] = (string) ($levelData['income_rate']??'10');

        //代理级别数据
        $levelDatas = [];
        foreach([1,2,3] as $k){
            $levelData = config('param.vip_level'.$k);
            if (is_null($levelData)) {
                continue;
            }
            $levelDatas['vip_level_'.$k] = $levelData;
            //更新代理级别
            if($user['invite_user_nums']>=$levelData['invite_number'] && $user['agent_level']<$k){
                $update = [
                    'agent_level'=>$k,
                    'upgrade_agent_time'=>date('Y-m-d H:i:s')
                ];
                (new UserModel())->getWhere(['user_sn'=>$user['user_sn']])->update($update);
            }
        }
        $user['agent_level_data'] = $levelDatas;

        //是否为推广用户
        $user['is_promotion_user'] = '0';
        if($user['callback_param']){
            $user['is_promotion_user'] = '1';
        }
        //是否匿名游客
        $user['is_visitor'] = $user['mobile'] ? '0' : '1';

        $this->unsetCommonFields($user,['source_ip','login_time','scene','userid','weixin_unionid','ios_unionid','google_unionid','updated_at','callback_param']);

        return $user;
    }

    public function outLogin()
    {
        if(request()->uuid==false){
            throw new IndexException(4020,'设备号不能空');
        }

        $user = UserModel::getInstance()->getWhere(['user_sn'=>request()->curUserId])->first();
        if(empty($user['mobile'])){
            throw new IndexException(4020,'已绑定手机号的用户才可退出');
        }

        $user = UserModel::getInstance()->getWhere(['uuid'=>request()->uuid,['mobile','=',''],'isdel'=>0])->first();
        if($user){
            //该设备已存在游客账号直接切换即可
            $user = $this->getInfo($user);
            $user = $this->loginData($user);
        }else{
            //重新注册一个游客用户
            $user = $this->createUser(['uuid'=>request()->uuid]);
            $user = $this->loginData($user);
        }

        Cache::set('delete_user_' . md5(request()->token), 1, 43200);

        return $user;
    }

    public function updateUserInfo($param)
    {
        $user = UserModel::getInstance()->getWhere(['user_sn'=>request()->curUserId])->first();

        !empty($param['nickname']) && $user->nickname = $param['nickname'];
        !empty($param['headimgurl']) && $user->headimgurl = $param['headimgurl'];
        if(!empty($param['scene'])){
            if($param['scene']==$user['invit_code']){
                throw new IndexException(4020,'不能填写自己邀请码');
            }
            if($user->scene){
                throw new IndexException(4020,'已有邀请人,不能重复更新邀请人');
            }
            $user->scene = $param['scene'];
            if($param['scene']==config('param.one_level_Invit_code')){
                $user->parentid = 0;
            }else{
                $parentidFirst = UserModel::getInstance()->getWhere(['invit_code'=>$param['scene'],'isdel'=>0])->first(['user_sn','user_role']);
                if($parentidFirst==false){
                    throw new IndexException(4020,'邀请码错误');
                }
                $user->parentid = $parentidFirst['user_sn'];
                $parentidFirst['user_role']==2 && $user->user_role = 1;
            }

            //如果有邀请人，则更新邀请人数
            if(!empty($user->parentid)){
                UserModel::getInstance()->updateInviteUserNums($user['user_sn'],$user->parentid,1);
            }
        }

        return $user->save();
    }

    public function deleteUser()
    {
        if(request()->uuid==false){
            throw new IndexException(4020,'设备号不能空');
        }

        $user = UserModel::getInstance()->getWhere(['user_sn'=>request()->curUserId])->first();
        if(empty($user['mobile'])){
            throw new IndexException(4020,'已绑定手机号的用户才可注销');
        }

        try{
            Db::connection('mysql')->beginTransaction();
            $user = UserModel::getInstance()->getWhere(['uuid'=>request()->uuid,['mobile','=',''],'isdel'=>0])->first();
            if($user){
                //该设备已存在游客账号直接切换即可
                $user = $this->getInfo($user);
                $user = $this->loginData($user);
            }else{
                //重新注册一个游客用户
                $user = $this->createUser(['uuid'=>request()->uuid]);
                $user = $this->loginData($user);
            }

            $update = [
                'isdel'=>time(),
                'status'=>2,
            ];
            UserModel::getInstance()->getWhere(['user_sn'=>request()->curUserId])->update($update);

            Db::connection('mysql')->commit();

            Cache::set('delete_user_' . md5(request()->token), 1, 43200);
            return $user;
        }catch (\Throwable $e){
            Db::connection('mysql')->rollBack();
            throw $e;
        }
    }

    public function getUserTeamTotal($param)
    {
        $data = [];
        //最近30天的新增人数
        $where = ['user_sn'=>request()->curUserId,['level','<',2],['created_at','>',date('Y-m-d',strtotime('-29 day'))]];
        $data['thirty_days_user_nums'] = UserInviteRelationModel::getInstance()->getWhere($where)->count();

        //团队人数
        $where = ['user_sn'=>request()->curUserId,['level','<',2]];
        $data['total_user_nums'] = UserInviteRelationModel::getInstance()->getWhere($where)->count();

        return $data;
    }

    public function getUserTeamList($param)
    {
        $where = ['user_sn'=>request()->curUserId];
        if(!empty($param['level'])){
            $where[] = ['level','=',$param['level']];
        }else{
            $where[] = ['level','=',1];
        }
        $inviteUserMap = UserInviteRelationModel::getInstance()->getWhere($where)->pluck('level','invite_user_sn')->toArray();

        //获取邀请用户数据
        $where = [['user_sn','in',array_keys($inviteUserMap)]];
        if(!empty($param['keyword'])){
            $where[] = ['or'=>[
                [['mobile','=',$param['keyword']]],
                [['nickname','like',"%{$param['keyword']}%"]]
            ]
            ];
        }
        $userData = UserModel::getInstance()->getWhere($where)->get(['user_sn','nickname','headimgurl','agent_level','created_at'])->toArray();

        $userSns = array_column($userData,'user_sn');

        //今日收益
        $startTime = date('Y-m-d 00:00:00');
        $endTime = date('Y-m-d H:i:s');
        $where = ['type'=>1,['user_sn','in',$userSns]];
        $where[] = ['income_date','between',[$startTime,$endTime]];
        $field = ['user_sn',Db::raw('sum(amount) total_income')];
        $todayIncomeData = UserIncomeFlowModel::getInstance()->getWhere($where)->select($field)->groupBy('user_sn')->get()->toArray();
        $todayIncomeMap = array_column($todayIncomeData,null,'user_sn');

        //本月收益
        $startTime = date('Y-m-01 00:00:00');
        $endTime = date('Y-m-d H:i:s');
        $where = ['type'=>1,['user_sn','in',$userSns]];
        $where[] = ['income_date','between',[$startTime,$endTime]];
        $field = ['user_sn',Db::raw('sum(amount) total_income')];
        $monthIncomeData = UserIncomeFlowModel::getInstance()->getWhere($where)->select($field)->groupBy('user_sn')->get()->toArray();
        $monthIncomeMap = array_column($monthIncomeData,null,'user_sn');

        //累计收益
        $where = ['type'=>1,['user_sn','in',$userSns]];
        $field = ['user_sn',Db::raw('sum(amount) total_income')];
        $totalIncomeData = UserIncomeFlowModel::getInstance()->getWhere($where)->select($field)->groupBy('user_sn')->get()->toArray();
        $totalIncomeMap = array_column($totalIncomeData,null,'user_sn');

        //我的收益
        $where = ['type'=>1,'user_sn'=>request()->curUserId,['income_source_user_sn','in',$userSns]];
        $field = ['income_source_user_sn',Db::raw('sum(amount) total_income')];
        $myIncomeData = UserIncomeFlowModel::getInstance()->getWhere($where)->select($field)->groupBy('income_source_user_sn')->get()->toArray();
        $myIncomeMap = array_column($myIncomeData,null,'income_source_user_sn');

        //今日直邀
        $startTime = date('Y-m-d 00:00:00');
        $endTime = date('Y-m-d H:i:s');
        $where = ['level'=>1,['user_sn','in',$userSns]];
        $where[] = ['created_at','between',[$startTime,$endTime]];
        $field = ['user_sn',Db::raw('count(user_sn) invite_counts')];
        $todayInviteData = UserInviteRelationModel::getInstance()->getWhere($where)->groupBy('user_sn')->get($field)->toArray();
        $todayInviteMap = array_column($todayInviteData,null,'user_sn');

        //本月直邀
        $startTime = date('Y-m-01 00:00:00');
        $endTime = date('Y-m-d H:i:s');
        $where = ['level'=>1,['user_sn','in',$userSns]];
        $where[] = ['created_at','between',[$startTime,$endTime]];
        $field = ['user_sn',Db::raw('count(user_sn) invite_counts')];
        $monthInviteData = UserInviteRelationModel::getInstance()->getWhere($where)->groupBy('user_sn')->get($field)->toArray();
        $monthInviteMap = array_column($monthInviteData,null,'user_sn');

        foreach($userData as &$val){
            $val['level_type'] = $inviteUserMap[$val['user_sn']] ?? 2;
            $val['today_income'] = (string)(isset($todayIncomeMap[$val['user_sn']])? $todayIncomeMap[$val['user_sn']]['total_income']:'0');
            $val['month_income'] = (string)(isset($monthIncomeMap[$val['user_sn']])? $monthIncomeMap[$val['user_sn']]['total_income']:'0');
            $val['total_income'] = (string)(isset($totalIncomeMap[$val['user_sn']])? $totalIncomeMap[$val['user_sn']]['total_income']:'0');
            $val['my_income'] = (string)(isset($myIncomeMap[$val['user_sn']])? $myIncomeMap[$val['user_sn']]['total_income']:'0');
            $val['today_invite_users'] = (string)(!empty($todayInviteMap[$val['user_sn']]['invite_counts'])? $todayInviteMap[$val['user_sn']]['invite_counts']:'0');
            $val['month_invite_users'] = (string)(!empty($monthInviteMap[$val['user_sn']]['invite_counts'])? $monthInviteMap[$val['user_sn']]['invite_counts']:'0');
        }

        //排序
        $param['order'] = $param['order'] ?? 'created_at';
        if($param['order']=='month'){
            $userData = $this->arraySort($userData,'month_income','desc');
        }elseif($param['order']=='total'){
            $userData = $this->arraySort($userData,'total_income','desc');
        }elseif($param['order']=='today'){
            $userData = $this->arraySort($userData,'today_income','desc');
        }else{
            $userData = $this->arraySort($userData,'created_at','desc');
        }

        list($listData,$data) = $this->buildPage($userData);
        $data['data'] = $listData;

        return $data;
    }

    public function getUserTeamDetail($param)
    {
        $fields = ['user_sn','parentid','nickname','headimgurl','agent_level', 'created_at','upgrade_agent_time'];
        $user = UserModel::getInstance()->getWhere(['user_sn'=>$param['user_sn']])->first($fields);
        if($user==false){
            throw new IndexException(4020,'用户不存在');
        }

        //今日收益
        $startTime = date('Y-m-d 00:00:00');
        $endTime = date('Y-m-d H:i:s');
        $where = ['type'=>1,'user_sn'=>$param['user_sn']];
        $where[] = ['income_date','between',[$startTime,$endTime]];
        $user['today_income'] = (string)UserIncomeFlowModel::getInstance()->getWhere($where)->sum('amount');

        //本月收益
        $startTime = date('Y-m-01 00:00:00');
        $endTime = date('Y-m-d H:i:s');
        $where = ['type'=>1,'user_sn'=>$param['user_sn']];
        $where[] = ['income_date','between',[$startTime,$endTime]];
        $user['month_income'] = (string)UserIncomeFlowModel::getInstance()->getWhere($where)->sum('amount');

        //累计收益
        $where = ['type'=>1,'user_sn'=>$param['user_sn']];
        $user['total_income'] = (string)UserIncomeFlowModel::getInstance()->getWhere($where)->sum('amount');

        //今日直邀
        $startTime = date('Y-m-d 00:00:00');
        $endTime = date('Y-m-d H:i:s');
        $where = ['level'=>1,'user_sn'=>$param['user_sn']];
        $where[] = ['created_at','between',[$startTime,$endTime]];
        $user['today_invite_users'] = (string)UserInviteRelationModel::getInstance()->getWhere($where)->count();

        //本月直邀
        $startTime = date('Y-m-01 00:00:00');
        $endTime = date('Y-m-d H:i:s');
        $where = ['level'=>1,'user_sn'=>$param['user_sn']];
        $where[] = ['created_at','between',[$startTime,$endTime]];
        $user['month_invite_users'] = (string)UserInviteRelationModel::getInstance()->getWhere($where)->count();

        //累计直邀
        $where = ['level'=>1,'user_sn'=>$param['user_sn']];
        $user['total_invite_users'] = (string)UserInviteRelationModel::getInstance()->getWhere($where)->count();

        //直邀者
        $parentUser = false;
        if($user['parentid']>0){
            $parentUser = UserModel::getInstance()->getWhere(['user_sn'=>$user['parentid']])->first(['user_sn','nickname']);
        }
        if($parentUser){
            $user['direct_invite_user'] = $parentUser->toArray();
        }else{
            $user['direct_invite_user'] = ['user_sn'=>0,'nickname'=>''];
        }

        //所属v2 v3
        $parentUser = $this->userTreeVip($user['parentid'],1);
        if($parentUser && $parentUser['agent_level']==3){
            $user['parent_v2'] = $parentUser;
            $user['parent_v3'] = $parentUser;
        }elseif($parentUser && $parentUser['agent_level']==2){
            $user['parent_v2'] = $parentUser;
            $parentUser2 = $this->userTreeVip($parentUser['parentid'],2);
            if($parentUser2 && $parentUser2['agent_level']==3){
                $user['parent_v3'] = $parentUser2;
            }
        }
        !isset($user['parent_v2'] ) && $user['parent_v2'] = ['user_sn'=>0,'nickname'=>''];
        !isset($user['parent_v3'] ) && $user['parent_v3'] = ['user_sn'=>0,'nickname'=>''];

        //最近邀请时间
        $user['last_invite_time'] = UserInviteRelationModel::getInstance()->getWhere(['user_sn'=>$param['user_sn']])->max('created_at');
        $user['last_invite_time'] = $user['last_invite_time']?:'';
        //升级时间
        $user['upgrade_agent_time'] = $user['upgrade_agent_time']?:'';

        //        $fields = ['user_sn','agent_level','is_platform_teacher','is_channel_business'];
        //        $userFirst = UserModel::getInstance()->getWhere(['user_sn'=>request()->curUserId])->first($fields);
        //        if($userFirst['is_platform_teacher']==1 || $userFirst['is_channel_business']==1 || $userFirst['agent_level']==3){
        //            $user['is_teacher'] = 1;
        //        }else{
        //            $user['is_teacher'] = 0;
        //        }
        //$user['is_teacher'] = 1;

        return $user;
    }

    protected function userTreeVip($parentid,$agentLevel=1)
    {
        if(empty($parentid)){
            return [];
        }
        $parentUser = UserModel::getInstance()->getWhere(['user_sn'=>$parentid])->first(['user_sn','parentid','nickname','agent_level']);
        if($parentUser){
            if($agentLevel<$parentUser['agent_level']){
                return $parentUser;
            }
            if($parentUser['parentid']>0){
                return $this->userTreeVip($parentUser['parentid'],$agentLevel);
            }
        }

        return [];
    }

    public function oneKeyLogin($param)
    {
        $param['accessToken'] = $param['yi_dun_access_token'];
        $param['token'] = $param['yi_dun_token'];
        $mobile = YidunQuickPass::getInstance()->oneKeyLogin($param);

        $isBang = UserModel::getInstance()->getWhere(['user_sn'=>request()->curUserId,'isdel'=>0])->value('mobile');
        if($isBang){
            throw new IndexException(4020,'游客情况下,才能进行绑定');
        }

        $mobileUser = UserModel::getInstance()->getWhere(['mobile'=>$mobile,'isdel'=>0])->first();
        //获取最后一次登录设备
        $uuid = '';
        $mobileUser && $uuid = UserLoginRecordModel::getInstance()->getWhere(['user_sn'=>$mobileUser['user_sn']])->orderBy('loginid','desc')->value('uuid');
        if($mobileUser==false){
            //手机号还没绑定过，进到绑定填写邀请码页
            throw new IndexException(4000,$mobile);
        }elseif(request()->curUserId==false){
           //兼容之前做法，用户未登录，直接登录操作删掉原来二维码就重新生成
            if($mobileUser['qrcode_image']==false){
                //生成邀请二维码
                $qrcodeImage = QRCodeImage::getInstance()->generateUserQrCode(config('param.invit_qrcode_link','').'?code='.$mobileUser['invit_code']);
                $mobileUser->qrcode_image = $qrcodeImage;
                $mobileUser->save();
            }

            $mobileUser = $this->getInfo($mobileUser);
            $user = $this->loginData($mobileUser);
            return $user;
        }elseif($mobileUser['uuid']==false){
            //没有设备号，是H5手机号注册的，不用进入邀请页了，直接调用绑定登录接口
            throw new IndexException(4001,$mobile);
        }elseif($uuid==false || request()->uuid==$uuid){
            //同一台设备，不用进入邀请页了，直接调用绑定登录接口
            throw new IndexException(4001,$mobile);
        }elseif(request()->uuid!=$uuid){
            //不同一台设备，不用进入邀请页了，直接调用绑定登录接口,但是需要给用户提示
            throw new IndexException(5000,$mobile);
        }
    }

    public function loginByAuthcode($param)
    {
        if(Common::checkMobile($param['mobile'])==false){
            throw new IndexException(4020,'手机号码错误');
        }

        if(empty($param['authcode'])){
            throw new IndexException(4020,'验证码不能为空');
        }

        if(in_array($param['mobile'],['16227020507'])){
            //这个是给ios审核测试的手机号，给个固定的验证码6688
            if($param['authcode']!='6688'){
                throw new IndexException(4020,'验证码错误');
            }
        }else{
            $cache = Cache::get('sms_auth_code_login_'.$param['mobile']);
            if(!$cache || $param['authcode']!=$cache['code']){
                throw new IndexException(4020,'验证码错误');
            }
        }

        $isBang = UserModel::getInstance()->getWhere(['user_sn'=>request()->curUserId,'isdel'=>0])->value('mobile');
        if($isBang){
            throw new IndexException(4020,'游客情况下,才能进行绑定');
        }

        $mobile = $param['mobile'];
        $mobileUser = UserModel::getInstance()->getWhere(['mobile'=>$mobile,'isdel'=>0])->first();
        //获取最后一次登录设备
        $uuid = '';
        $mobileUser && $uuid = UserLoginRecordModel::getInstance()->getWhere(['user_sn'=>$mobileUser['user_sn']])->orderBy('loginid','desc')->value('uuid');
        if($mobileUser==false){
            //手机号还没绑定过，进到绑定填写邀请码页
            throw new IndexException(4000,$mobile);
        }elseif(request()->curUserId==false){
            //兼容之前做法，用户未登录，直接登录操作删掉原来二维码就重新生成
            if($mobileUser['qrcode_image']==false){
                //生成邀请二维码
                $qrcodeImage = QRCodeImage::getInstance()->generateUserQrCode(config('param.invit_qrcode_link','').'?code='.$mobileUser['invit_code']);
                $mobileUser->qrcode_image = $qrcodeImage;
                $mobileUser->save();
            }

            $mobileUser = $this->getInfo($mobileUser);
            $user = $this->loginData($mobileUser);
            return $user;
        }elseif($mobileUser['uuid']==false){
            //没有设备号，是H5手机号注册的，不用进入邀请页了，直接调用绑定登录接口
            throw new IndexException(4001,$mobile);
        }elseif($uuid==false || request()->uuid==$uuid){
            //同一台设备，不用进入邀请页了，直接调用绑定登录接口
            throw new IndexException(4001,$mobile);
        }elseif(request()->uuid!=$uuid){
            //不同一台设备，不用进入邀请页了，直接调用绑定登录接口,但是需要给用户提示
            throw new IndexException(5000,$mobile);
        }
    }

    public function registerToScene($param)
    {
        if(Common::checkMobile($param['mobile'])==false){
            throw new IndexException(4020,'手机号码错误');
        }

        $key = 'user_register_' . $param['mobile'];
        if (Common::setnx($key) == false) {
            //throw new IndexException(4020, '请不要频繁操作');
        }

        if(request()->uuid==false){
            throw new IndexException(4020,'设备号不能空');
        }

        $user = UserModel::getInstance()->getWhere(['user_sn'=>request()->curUserId,'isdel'=>0])->first();
        if($user && !empty($user['mobile'])){
            //throw new IndexException(4020,'游客情况下,才能进行绑定');

            //如果已经是登录情况下直接返回登录
            $user = $this->getInfo($user);
            //要把之前该手机号的用户产生的token全部删除，做到单点登录
            //UserLoginRecordModel::getInstance()->getWhere(['user_sn'=>$user['user_sn']])->update(['isdel'=>time()]);
            return $this->loginData($user);
        }

        //        if(cache()->get('user_register_key_'.$param['mobile'])==false){
        //            throw new IndexException(4020,'非法操作');
        //        }

        $mobileUser = UserModel::getInstance()->getWhere(['mobile' => $param['mobile']])->orderBy('created_at', 'desc')->first();
         if ($mobileUser && $mobileUser['isdel'] > 0) {
            $delDate = strtotime(date('Y-m-d', $mobileUser['isdel']));
            $nowDate = strtotime(date('Y-m-d'));
            $days    = floor(($nowDate - $delDate) / 86400);
            if ($days <= 7) {
                throw new IndexException(4020, '从注销天起7天后才能重新绑定');
            }
        }

        if($user==false && ($mobileUser==false || $mobileUser['isdel']>0)){
            //todo 1 当前用户未登录和手机号也未注册情况下，重新注册用户
            $parentid = 0;
            $userRole = 0;
            if(!empty($param['scene'])){
                $parentidFirst = UserModel::getInstance()->getWhere(['invit_code' => $param['scene'], 'isdel' => 0])->first(['user_sn', 'user_role']);
                if ($parentidFirst == false && Common::isAuthInviteCode() == true) {
                    //throw new IndexException(4020, '邀请码错误');
                }
                if ($parentidFirst) {
                    $parentid = $parentidFirst['user_sn'] ?? 0;

                    //如果上级是商务，则该用户为渠道商
                    $parentidFirst['user_role'] == 2 && $userRole = 1;
                }
            }
            $param['parentid'] = $parentid;
            $param['user_role'] = $userRole;
            $param['uuid'] = request()->uuid;
            $user = $this->createUser($param);

            //要把之前该手机号的用户产生的token全部删除，做到单点登录
            UserLoginRecordModel::getInstance()->getWhere(['user_sn'=>$user['user_sn']])->update(['isdel'=>time()]);
            
            return $this->loginData($user);
        }elseif ($user==false && $mobileUser && $mobileUser['isdel']==0){
            //todo 2 当前用户未登录,手机号已经注册情况下，绑定当前uuid.
            $mobileUser->uuid = request()->uuid;
            $mobileUser->save();

            $user = $this->getInfo($mobileUser);
            //要把之前该手机号的用户产生的token全部删除，做到单点登录
            UserLoginRecordModel::getInstance()->getWhere(['user_sn'=>$mobileUser['user_sn']])->update(['isdel'=>time()]);

            return $this->loginData($user);
        }elseif($user && $mobileUser && $mobileUser['isdel']==0){
            //todo 3 当前用户已登陆，并且手机号已注册情况下
            //没有设备号，是H5手机号注册的，把手机号和邀请信息移到当前账号下绑定
            if($mobileUser['uuid']==false){
                try{
                    Db::connection('mysql')->beginTransaction();
                    //角色和上级id
                    $userRole = 0;
                    $parentid = 0;
                    if(!empty($mobileUser['scene'])){
                        $parentidFirst = UserModel::getInstance()->getWhere(['invit_code' => $mobileUser['scene'], 'isdel' => 0])->first(['user_sn', 'user_role']);
                        if ($parentidFirst == false && Common::isAuthInviteCode() == true) {
                            //throw new IndexException(4020, '邀请码错误');
                        }
                        if ($parentidFirst) {
                            $parentid = $parentidFirst['user_sn'] ?? 0;

                            //如果上级是商务，则该用户为渠道商
                            $parentidFirst['user_role'] == 2 && $userRole = 1;
                        }
                    }
                    $user->mobile = $param['mobile'];
                    $user->user_role = $userRole??0;
                    $user->parentid = $parentid??0;
                    $user->save();

                    //如果有邀请人，则更新邀请人数
                    if(!empty($parentid)){
                        UserModel::getInstance()->updateInviteUserNums($user['user_sn'],$parentid,1);
                    }

                    //领取一天vip，是否领取然后进来注册，然后直接开通vip
                    if(!empty($param['mobile'])){
                        $receive = ReceiveVipByMobileModel::getInstance()->getWhere(['mobile'=>$param['mobile']])->first();
                        if($receive){
                            $this->receiveVipOpen($user,$receive['id']);
                        }
                    }
                    //删除从H5注册进来的用户-直接物理删除
                    $mobileUser->delete();
                    Db::connection('mysql')->commit();
                    $mobileUser = $user;
                }catch (\Throwable $e){
                    Db::connection('mysql')->rollBack();
                    throw $e;
                }
            }

            //要把之前该手机号的用户产生的token全部删除，做到单点登录
            UserLoginRecordModel::getInstance()->getWhere(['user_sn'=>$mobileUser['user_sn']])->update(['isdel'=>time()]);

            $mobileUser = $this->getInfo($mobileUser);
            return $this->loginData($mobileUser);
        }

        //todo 4 走到这里是要绑定手机号操作
        try{
            Db::connection('mysql')->beginTransaction();
            $userRole = 0;
            $parentid = 0;
            if(!empty($param['scene'])){
                $parentidFirst = UserModel::getInstance()->getWhere(['invit_code' => $param['scene'], 'isdel' => 0])->first(['user_sn', 'user_role']);
                if ($parentidFirst == false && Common::isAuthInviteCode() == true) {
                    //throw new IndexException(4020, '邀请码错误');
                }
                if ($parentidFirst) {
                    $parentid = $parentidFirst['user_sn'] ?? 0;

                    //如果上级是商务，则该用户为渠道商
                    $parentidFirst['user_role'] == 2 && $userRole = 1;
                }
            }

            $mobileStr = substr($param['mobile'],-6);
            $user->nickname = '推文_'.$mobileStr;
            $user->mobile = $param['mobile'];
            $user->user_role = $userRole??0;
            $user->parentid = $parentid??0;
            $user->save();

            //如果有邀请人，则更新邀请人数
            if(!empty($parentid)){
                UserModel::getInstance()->updateInviteUserNums($user['user_sn'],$parentid,1);
            }

            //领取一天vip，是否领取然后进来注册，然后直接开通vip
            if(!empty($param['mobile'])){
                $receive = ReceiveVipByMobileModel::getInstance()->getWhere(['mobile'=>$param['mobile']])->first();
                if($receive){
                    $this->receiveVipOpen($user,$receive['id']);
                }
            }

            //要把之前该手机号的用户产生的token全部删除，做到单点登录
            $mobileUser && UserLoginRecordModel::getInstance()->getWhere(['user_sn'=>$mobileUser['user_sn']])->update(['isdel'=>time()]);
            Db::connection('mysql')->commit();

            //加入巨量引擎注册回传队列
            //生成唯一的请求id
            $requestId = Common::getRequestId();
            // 队列名
            $queue = 'oceanengineAttribution';
            // 数据，可以直接传数组，无需序列化
            $data = ['requestid' => $requestId, 'user_sn'=>$user['user_sn'],'device'=>request()->deviceNumber ,'type'=>'register' ,'platform'=>request()->platform];
            // 投递消息
            RedisQueue::getInstance()->sendQueue($queue, $data);

            $user = $this->getInfo($user);
            return $this->loginData($user);
        }catch (\Throwable $e){
            Db::connection('mysql')->rollBack();
            throw $e;
        }
    }

    public function consumePointCheck($param)
    {
        $plan = [];
        $spreadType = 0;
        if(!empty($param['novel_sn'])){
            $novel = NovelSpreadModel::getInstance()->getWhere(['novel_sn'=>$param['novel_sn']])->first();
            if ($novel == false) {
                throw new IndexException(4020, '当前推文不可用');
            }
            $spreadType = $novel['spread_type'];
            if($spreadType==3){
                //海外小说
                //随机获取当前小说的一个方案
                $where = ['isdel' => 0, 'status' => 1, 'novel_sn' => $param['novel_sn']];
                $plan = PromotionPlanOverseaModel::getInstance()->getWhere($where)->orderBy(Db::Raw('RAND()'))->first();
                if ($plan == false) {
                    throw new IndexException(4020, '当前推文不可用');
                }
                if ($plan['part_nums'] == false) {
                    throw new IndexException(4020, '当前推文不可用');
                }

                $batch = NovelChapterPartOverseaModel::getInstance()->getWhere(['planid'=>$plan['id'],'isdel'=>0])->max('batch');
                $partNums = NovelChapterPartOverseaModel::getInstance()->getWhere(['planid'=>$plan['id'],'isdel'=>0,'batch'=>$batch])->count();
                PromotionPlanOverseaModel::getInstance()->getWhere(['id'=>$plan['id']])->update(['part_nums'=>$partNums]);

                $contents = NovelChapterPartOverseaModel::getInstance()->getWhere(['planid'=>$plan['id'],'isdel'=>0,'batch'=>$batch])->pluck('content');
                $strLen = 0;
                foreach ($contents as $content){
                    $strLen += mb_strlen($content);
                }
                $totoalPonint = round(ceil($strLen/3000)*10);
                $param['consume_point'] = $totoalPonint;
            }else{
                //水上、水下小说
                //随机获取当前小说的一个方案
                $where = ['isdel' => 0, 'status' => 1, 'novel_sn' => $param['novel_sn']];
                $plan = PromotionPlanModel::getInstance()->getWhere($where)->orderBy(Db::Raw('RAND()'))->first();
                if ($plan == false) {
                    throw new IndexException(4020, '当前推文不可用');
                }
                if ($plan['part_nums'] == false) {
                    throw new IndexException(4020, '当前推文不可用');
                }

                $batch = NovelChapterPartModel::getInstance()->getWhere(['planid'=>$plan['id'],'isdel'=>0])->max('batch');
                $partNums = NovelChapterPartModel::getInstance()->getWhere(['planid'=>$plan['id'],'isdel'=>0,'batch'=>$batch])->count();
                //PromotionPlanModel::getInstance()->getWhere(['id'=>$plan['id']])->update(['part_nums'=>$partNums]);
                $totoalPonint = round($partNums*config('param.image_consume_point',10));
                $param['consume_point'] = $totoalPonint;
            }
        }

        $data = parent::consumePoint($param);

        $consumeVipPointText = '0';
        $consumeBuyPointText = '0';
        $balanceVipPointText = '0';
        $balanceBuyPointText = '0';

        $data['consume_vip_point']>0 && $consumeVipPointText = (string)$data['consume_vip_point'];
        $data['consume_vip_point']>0 && $balanceVipPointText = (string)$data['balance_vip_point'];
        $data['consume_buy_point']>0 &&  $consumeBuyPointText = (string)$data['consume_buy_point'];
        $data['consume_buy_point']>0 &&  $balanceBuyPointText = (string)$data['balance_buy_point'];

        $data = [
            'consume_vip_point_text'=>$consumeVipPointText,
            'consume_buy_point_text'=>$consumeBuyPointText,
            'balance_vip_point_text'=>$balanceVipPointText,
            'balance_buy_point_text'=>$balanceBuyPointText,
            'planid'=>(int)($plan['id']??0),
            'spread_type'=>(int)($spreadType),
        ];
        return $data;
    }

    public function getUserWorksList($param)
    {
        $field = ['id','user_sn','novel_sn','created_at','status','plan_type','spread_type','is_example',
                  'example_main_image','video_url','material_data','material_video','image'];
        $where = [
            'user_sn'=>request()->curUserId,
            'isdel'=>0,
        ];
        if(!empty($param['novel_sn'])){
            $where['novel_sn'] = $param['novel_sn'];
        }
        if(!empty($param['plan_type'])){
            $where['plan_type'] = $param['plan_type'];
        }
        if(!empty($param['is_example'])){
            $where['is_example'] = $param['is_example'];
            unset($where['user_sn']);
        }
        $data = UserPromotionPlanModel::getInstance()->getWhere($where)->orderBy('created_at','desc')->get($field);
        $novelMap = $this->getListMapById(NovelSpreadModel::getInstance(),$data->toArray(),'novel_sn','novel_sn',['field'=>['task_status','under_status','spread_type','novel_sn']]);
        $imageFirstNums = 0;
        foreach ($data as $val){
            $image = '';
            if(empty($val['image'])){
                $part = UserNovelChapterPartModel::getInstance()->getWhere(['user_planid'=>$val['id']])->orderBy('number','asc')->first();
                $image = $val['image'] = $part['ai_images'];
            }

            if(isset($novelMap[$val['novel_sn']])){
                $val['spread_type'] = $novelMap[$val['novel_sn']]['spread_type'];
            }
            $val['under_status'] = isset($novelMap[$val['novel_sn']])?$novelMap[$val['novel_sn']]['under_status']:1;
            $val['task_status'] = isset($novelMap[$val['novel_sn']])?$novelMap[$val['novel_sn']]['task_status']:2;
            if($val['spread_type']==2){
                $val['task_status'] = $val['under_status']==1 ? 2 : 0;
            }

            $val['video'] = '';
            $val['created_at'] = substr(str_replace(['-',' ',':'],'',$val['created_at']),0,-2);
           if($val['plan_type']==1){
               $val['name'] = '一键推文';
           }elseif ($val['plan_type']==2){
               $val['name'] = '漫画推文';
           }elseif ($val['plan_type']==3){
               $val['name'] = '爆款二创';
           }elseif ($val['plan_type']==4){
               $val['name'] = '海外推文';

               $materialVideo = $val['material_video'] ? json_decode($val['material_video'],true) : [];
               if($materialVideo){
                   $val['video'] = $materialVideo[0];
               }else{
                   $materialData = $val['material_data'] ? json_decode($val['material_data'],true) : [];
                   $val['video'] = $materialData['heigh'][0]['video']??'';
                   $val['video'] = $val['video']?:($materialData['normal'][0]['video']??'');
               }
               if(empty($val['image']) && !empty($val['video']) && $imageFirstNums<5){
                   //获取第一帧作为封面图
                   $saveImageFile = runtime_path('hinovel_work_image_'.Common::getRequestId().'.png');
                   try{
                       $imageFirstNums++;
                       (new Ffmpeg())->getFirstVideoToImage($saveImageFile,$val['video']);
                       if(is_file($saveImageFile)){
                           $res = TencentCos::getInstance()->uploadStringFile($saveImageFile,'user_work/image');
                           if(!empty($res['Location'])){
                               $image = 'https://'.$res['Location'];
                               $val['image'] = $image;
                           }
                           unlink($saveImageFile);
                       }
                   }catch (\Throwable $e){}
               }

           }

           $image && UserPromotionPlanModel::getInstance()->getWhere(['id'=>$val['id']])->update(['image'=>$image]);
           unset($val['material_data'],$val['material_video']);
        }

        return $data;
    }

    public function getUserWorksDetail($param)
    {
        $plan = UserPromotionPlanModel::find($param['user_planid']);
        if($plan==false){
            throw new IndexException(4020,'作品不存在');
        }
        $planParts = UserNovelChapterPartModel::getInstance()->getWhere(['user_planid'=>$param['user_planid']])->orderBy('number','asc')->get();

        $imagePath = '';
        foreach($planParts as $val){
            $subTitles = json_decode($val['subtitle'], true);
            $timbreVoiceInfo = json_decode($val['timbre_voice_info'], true);
            $texts = [];
            if(isset($timbreVoiceInfo[0]['BeginTime'])){
                $index = 0;
                $text = '';
                $startTime = '';
                foreach ($timbreVoiceInfo as $k => $v) {
                    $startTime==false && $startTime = $v['BeginTime'];

                    if($v['Text']=='^'){
                        $texts[] = [
                            'content' => $text,
                            'start'   => $startTime,
                            'end'     => $v['EndTime']
                        ];

                        $text = '';
                        $startTime = '';
                    }else{
                        $text .= $v['Text'];
                    }
                }

                if(count($texts)!=count($subTitles)){
                    foreach($subTitles as $subTitle) {
                        $subTitle = str_replace([',', '，', '。', '.', '?', '？', '!', '！', '[', ']', '【', '】', '—','-','_','——','：',':','^'], '', $subTitle);

                        //数字转汉字替换
                        preg_match_all('/\d+/',$subTitle,$subTitleData);
                        if(!empty($subTitleData[0])){
                            $searchNums = [];
                            $replaceNum = [];
                            foreach ($subTitleData[0] as $num){
                                $searchNums[] = $num;
                                $replaceNum[] = Common::numToChinese($num);
                            }
                            $subTitle = str_replace($searchNums, $replaceNum, $subTitle);
                        }

                        $text     = '';
                        $rows = [];
                        foreach ($timbreVoiceInfo as $k => $v) {
                            if($v['EndIndex']<=$index){
                                continue;
                            }

                            $text .= $v['Text'];
                            $text = str_replace([',', '，', '。', '.', '?', '？', '!', '！', '[', ']', '【', '】', '—','-', '_','——','：',':','^'], '', $text);
                            $rows[] = $v;
                            if ($subTitle == $text) {
                                $texts[] = [
                                    'content' => $subTitle,
                                    'start'   => $rows[0]['BeginTime'],
                                    'end'     => $rows[count($rows) - 1]['EndTime'],
                                ];
                                $index = $v['EndIndex'];
                                break;
                            }
                        }
                    }
                }

                $val['timbre_voice_start'] = $timbreVoiceInfo[0]['BeginTime'];
                $val['timbre_voice_end'] = $timbreVoiceInfo[count($timbreVoiceInfo)-1]['EndTime'];
            }else{
                $val['timbre_voice_start'] = $timbreVoiceInfo[0]['start'];
                $val['timbre_voice_end'] = $timbreVoiceInfo[count($timbreVoiceInfo)-1]['end'];
                $texts = $timbreVoiceInfo;
            }

            $val['texts'] = $texts;
            $aiImages = !empty($val['ai_images_arr']) ? json_decode($val['ai_images_arr'], true): ($val['ai_images']?[$val['ai_images']]:[]);

            $aiImagesFaceArr = !empty($val['ai_image_face_arr']) ? json_decode($val['ai_image_face_arr'], true) : [];
            if(empty($aiImagesFaceArr)){
                $aiImagesFaceArr = [];
                foreach ($aiImages as $image){
                    $aiImagesFaceArr[] = [
                        'link'=>$image,
                        'faceRectangles'=>[],
                    ];
                }
            }

            //根据时长去掉图片数量
            $n = floor(round($val['timbre_voice_end']-$val['timbre_voice_start'])/2000);
            $n==0 && $n=1;
            shuffle($aiImages);
            shuffle($aiImagesFaceArr);
            $val['ai_images_arr'] = array_slice($aiImages,0,$n);
            $val['ai_image_face_arr'] = array_slice($aiImagesFaceArr,0,$n);

            $imagePath==false && $imagePath = $val['ai_images_arr'][0]??'';
        }
        
        $status = $plan['status'];
        $this->unsetCommonFields($plan,['queue_time','finish_time','updated_at','created_at','name']);
        $this->unsetCommonFields($planParts,['timbre_voice_info','subtitle','updated_at']);

        $data = $plan;
        $music = BackgroundMusicModel::getInstance()->getWhere(['id'=>$plan['musicid']])->first();
        $data['music_url'] = $music['music_url']??'';
        $data['music_tts'] = $music['tts']??0;
        $data['status'] = $status;

        $data['parts'] = $planParts;
        if($data['plan_type']==1){
            $data['name'] = '一键推文';
        }elseif ($data['plan_type']==2){
            $data['name'] = '漫画推文';
        }elseif ($data['plan_type']==3){
            $data['name'] = '爆款二创';
        }elseif ($data['plan_type']==4){
            $data['name'] = '海外推文';
        }
        $data['transition'] = array_filter(explode("|@@|",config('param.meishe_transition_key','')));
        $data['clipanim'] = array_filter(explode("|@@|",config('param.meishe_part_clipanim','')));
        $data['clip_animtaion_scheme'] = (int) config('param.clip_animtaion_scheme',0);

        $width = $height = 1024;
        $imagePath && list($width,$height) = @getimagesize($imagePath);
        $data['image_width'] = (int) $width;
        $data['image_height'] = (int) $height;

        if(isset(Constant::VIDEO_RATE_WH_MAP[$data['video_rate']])){
            $withHeight = Constant::VIDEO_RATE_WH_MAP[$data['video_rate']];
            $data['video_width'] = (int) $withHeight[0];
            $data['video_height'] = (int) $withHeight[1];
        }else{
            $data['video_rate'] = '3:2';
            $data['video_width'] = (int) 1080;
            $data['video_height'] = (int) 720;
        }

        return $data;
    }

    /**
     * 作品详情-海外推广逻辑
     * @param $param
     * @return array[]
     * @throws IndexException
     */
    public function getUserWorksOverseaDetail($param)
    {
        $plan = UserPromotionPlanModel::find($param['user_planid']);
        if($plan==false){
            throw new IndexException(4020,'作品不存在');
        }
        $planParts = UserNovelChapterPartModel::getInstance()->getWhere(['user_planid'=>$param['user_planid']])->orderBy('number','asc')->get();

        $chatMessage = !empty($plan['chat_messages']) ? json_decode($plan['chat_messages'], true) : [];
        $chatImages = !empty($plan['chat_images']) ? json_decode($plan['chat_images'], true) : [];
        $paragraphs = [];
        $styleType = $plan['style_type'] ?? 1;
        $i = 0;
        foreach($planParts as $val){
            $paragraph = [];
            $timbreVoiceInfo = json_decode($val['timbre_voice_info'], true);

            $paragraph['duration'] = $timbreVoiceInfo[count($timbreVoiceInfo)-1]['end'];
            $paragraph['content'] = $val['content'];
            $paragraph['dub'] = $val['timbre_voice_url'];
            $paragraph['caption'] = [];

            //风格为3，4的时候
            if (in_array($styleType, [3, 4])) {
                $paragraph['chat_image'] =$chatImages[$i] ?? '' ;
                $paragraph['chat_message'] = $chatMessage[$i]  ?? '';
            }
            $i++;

            foreach($timbreVoiceInfo as $v){
                $content = $v['content']=='sp'?'':(str_replace([',','.','，','。','：',':','?','？','!','！','@','-','_','\\'],'',$v['content']));
                $paragraph['caption'][] = [
                    'start'=>$v['start'],
                    'end'=>$v['end'],
                    'text'=> strtoupper($content)
                ];
            }
            $paragraphs[] = $paragraph;
        }
        $status = $plan['status'];
        $this->unsetCommonFields($plan,['queue_time','finish_time','updated_at','created_at','name']);
        $this->unsetCommonFields($planParts,['timbre_voice_info','subtitle','updated_at']);

        //开始组装数据-分段数据
        $data = [
            'paragraphs' => $paragraphs,
        ];

        //背景音乐
        $music = BackgroundMusicModel::getInstance()->getWhere(['id'=>$plan['musicid']])->first();
        $data['bgm'] = $music['music_url']??'';
        $data['music_tts'] = $music['tts']??0;
        $data['status'] = $status;

        $data['name'] = '海外推文';

        //素材视频
        $data['mixed_material'] = $plan['material_video'] ? json_decode($plan['material_video'], true):[];
        if(empty($data['mixed_material'])){
            $materials = json_decode($plan['material_data'],true);
            if($materials==false){
                throw new IndexException(4020,'素材不能为空');
            }
            $partTts = array_sum(array_column($paragraphs,'duration'));
            $partTts = ceil($partTts/1000);

            //如果素材时长小于配音时长，随机补素材，达到素材视频时长大于等于配音时长
            $data['mixed_material'] = [];
            $heighData = $materials['heigh'];
            $materialTts = 0;
            foreach ($heighData as $material){
                if(in_array($material['video'],$data['mixed_material'])){
                    continue;
                }
                $data['mixed_material'][] = $material['video'];

                $materialTts = round($materialTts+$material['tts']);
                if($materialTts>=$partTts){
                    break;
                }
                //精品视频使用于前20秒
                if($materialTts>20){
                    break;
                }
            }
            if($materialTts<$partTts){
                //兜底操作，普通的没有就用精品顶替
                $normalData = !empty($materials['normal']) ? $materials['normal'] : $materials['heigh'];
                foreach ($normalData as $material){
                    if(in_array($material['video'],$data['mixed_material'])){
                        continue;
                    }
                    $data['mixed_material'][] = $material['video'];

                    $materialTts = round($materialTts+$material['tts']);
                    if($materialTts>=$partTts){
                        break;
                    }
                }

                if($materialTts<$partTts){
                    $n=0;
                    while (true){
                        shuffle($normalData);
                        foreach ($normalData as $material){
                            //检查去重
                            if(in_array($material['video'],$data['mixed_material'])){
                                continue;
                            }
                            $data['mixed_material'][] = $material['video'];

                            $materialTts = round($materialTts+$material['tts']);
                            if($materialTts>=$partTts){
                                break 2;
                            }
                        }
                        if($materialTts>=$partTts){
                            break;
                        }
                        if($n>20){
                            break;
                        }
                        $n++;
                    }
                }
            }

            if(!empty($data['mixed_material'])){
                UserPromotionPlanModel::getInstance()->getWhere(['id'=>$plan['id']])->update(['material_video'=>json_encode($data['mixed_material'])]);
            }
        }

        //视频比例
        $data['video_scale'] = $plan['video_rate'];
        if(isset(Constant::OVERSEA_VIDEO_RATE_WH_MAP[$plan['video_rate']])){
            $withHeight = Constant::OVERSEA_VIDEO_RATE_WH_MAP[$plan['video_rate']];
            $data['video_width'] = (int) $withHeight[0];
            $data['video_height'] = (int) $withHeight[1];
        }else{
            $data['video_scale'] = '9:16';
            $data['video_width'] = (int) 1080;
            $data['video_height'] = (int) 1920;
        }

        $data['id'] = $plan['id'];
        $data['fontid'] = $plan['fontid'];
        $data['novel_sn'] = $plan['novel_sn']??'';
        $data['plan_type'] = $plan['plan_type'];
        $data['spread_type'] = $plan['spread_type'];
        $data['video_url'] = $plan['video_url']??'';


        //视频设置
        $defaultFont = [
            '9:16'=>7,
            '3:4'=>10,
            '4:3'=>17,
        ];
        $data['music_volume'] = $plan['music_volume']??50;
        $data['caption_font'] = !empty($plan['font_size'])?$plan['font_size']:($defaultFont[$data['video_scale']]??7);
        $data['caption_x'] = $plan['caption_post_x'];
        $data['caption_y'] = $plan['caption_post_y'];
        $data['settings'] = $plan['settings'] ? json_decode($plan['settings'], true):[];
        $randomSettings = [
            "random_reduce_scale"=>0,
            "random_add_scale"=>0,
            "random_speed_scale"=>0,
            "random_mirror_scale"=>0,
            "random_hash_scale"=>0,
            "random_filter_scale"=>0,
            "random_effect_scale"=>0,
            "random_transition_scale"=>0,
            "random_enlarge_scale"=>0
        ];
        $data['settings'] = array_merge($data['settings'],$randomSettings);

        /*2024-09-18版本迭代更新*/
        $data['style_type'] = $plan['style_type'] ?? 1;
        $data['card_image'] = $plan['card_image'] ?? '';
        $data['card_remain'] = $plan['card_remain'] ?? 0;
        $data['card_top'] = $plan['card_top'] ?? 1;
        $data['card_content'] = $plan['card_content'] ?? '';
        $data['card_dub'] = $plan['card_dub'] ?? '';

        if ($styleType == 2) {
            $rollingImg = UserPromotionOverseaRollingImgModel::getInstance()
                ->getWhere([
                    ['user_sn', '=', $plan->user_sn],
                    ['user_planid', '=', $plan->id],
                ])->first()->toArray();
        }

        $data['rolling_images'] = !empty($rollingImg['images']) ? json_decode($rollingImg['images'], true) : [];

        return $data;
    }

    public function updatePlanStatus($plan,$partData)
    {
        //20240828 不需要这里检查更新状态，对数据库更新操作太频繁
//        $isFinish = true;
//        foreach($partData as $v){
//            if(empty($v['timbre_voice_url'])){
//                $isFinish = false;
//                break;
//            }
//            if(in_array($plan['plan_type'],[1,2,3])){
//                if(empty($v['ai_images'])){
//                    $isFinish = false;
//                    break;
//                }
//            }
//        }
//        if($isFinish==true){
//            $plan->finish_time = date('Y-m-d H:i:s');
//            $plan->status = 2;
//            $plan->save();
//        }
    }

    public function deleteUserPlan($param)
    {
        $userPlanIds = explode(',',$param['user_planid']);

        if($userPlanIds){
            UserPromotionPlanModel::getInstance()->getWhere([['id','in',$userPlanIds]])->update(['isdel'=>time()]);
        }

        return ['count'=>count($userPlanIds)];
    }

    public function updateUserPlanVideo($param)
    {
        $first = UserPromotionPlanModel::getInstance()->getWhere(['id'=>$param['user_planid']])->first();
        if($first==false){
            throw new IndexException(4020,'作品不存在');
        }

        $path = 'video';
        $path .= !empty($param['path']) ? '/'.trim($param['path'],'/') : '';
        $res = TencentCos::getInstance()->uploadFormFile(request()->file('file'),$path);
        $videoLink = 'https://'.$res['Location'];
        $first->video_url = $videoLink;
        $first->save();

        return $param['user_planid'];
    }

    public function registerByMobile($param)
    {
        if(Common::setnx('user_register_'.$param['mobile'])==false){
            throw new IndexException(4020,'请不要频繁操作');
        }
        if(Common::checkMobile($param['mobile'])==false){
            throw new IndexException(4020,'手机号码错误');
        }

        $cache = Cache::get('sms_auth_code_register_'.$param['mobile']);
        if(!$cache || $param['authcode']!=$cache['code']){
            throw new IndexException(4020,'验证码错误');
        }

        $mobileUser = UserModel::getInstance()->getWhere(['mobile'=>$param['mobile']])->orderBy('created_at','desc')->first();
        if($mobileUser && $mobileUser['isdel']==0){
            throw new IndexException(4020,'该手机号已注册绑定，请去登录');
        }elseif($mobileUser && $mobileUser['isdel']>0){
            $delDate = strtotime(date('Y-m-d',$mobileUser['isdel']));
            $nowDate = strtotime(date('Y-m-d'));
            $days = floor(($nowDate-$delDate)/86400);
            if($days<=7){
                throw new IndexException(4020,'从注销天起7天后才能重新注册');
            }
        }

        if ($param['scene'] == config('param.one_level_Invit_code')) {
            $parentid = 0;
        } else {
            $parentidFirst = UserModel::getInstance()->getWhere(['invit_code' => $param['scene'], 'isdel' => 0])->first(['user_sn', 'user_role']);
            if ($parentidFirst == false) {
                throw new IndexException(4020, '邀请码错误');
            }

            if ($parentidFirst) {
                $parentid = $parentidFirst['user_sn'] ?? 0;

                //如果上级是商务，则该用户为渠道商
                $parentidFirst['user_role'] == 2 && $param['user_role'] = 1;
            } else {
                $parentid = 0;
            }
        }

        $param['parentid'] = $parentid;

        return $this->createUser($param);
    }

    public function receiveVipByMobile($param)
    {
        if(Common::setnx('receiveVipByMobile_'.$param['mobile'])==false){
            throw new IndexException(4020,'请求频繁');
        }

        if(Common::checkMobile($param['mobile'])==false){
            throw new IndexException(4020,'手机号码错误');
        }

        $count = ReceiveVipByMobileModel::getInstance()->getWhere(['mobile'=>$param['mobile']])->count();
        if($count){
            throw new IndexException(4020,'该手机号已领取');
        }

        $user = UserModel::getInstance()->getWhere(['mobile'=>$param['mobile'],'isdel'=>0])->first();
        if($user){
            $user = $this->checkIsVip($user);
            if($user['is_vip']>0){
                throw new IndexException(4020,'该手机号已是会员');
            }elseif(!empty($user['vip_expire_time'])){
                throw new IndexException(4020,'该手机号不具备领取条件');
            }else{
               return $this->receiveVipOpen($user);
            }
        }else{
            $id = ReceiveVipByMobileModel::getInstance()->insertGetId(['mobile'=>$param['mobile']]);

            return [$id];
        }
    }

    protected function receiveVipOpen($user,$receiveId=0)
    {
        try{
            Db::connection('mysql')->beginTransaction();

            $receiveId==false && $receiveId = ReceiveVipByMobileModel::getInstance()->insertGetId(['mobile'=>$user['mobile']]);

            //首先判断是否领取过
            $where = [
                'user_sn'=>$user['user_sn'],
                'type'=>3,
                'way'=>1,
                'sourceid'=>$receiveId,
                'source_table'=>ReceiveVipByMobileModel::getInstance()->getTable(),
            ];
            $count = UserPointFlowModel::getInstance()->getWhere($where)->count();
            if($count){
                return false;
            }

            UserModel::getInstance()->getWhere(['mobile'=>$user['mobile'],'isdel'=>0])->update([
                'is_vip'=>2,
                'vip_expire_time'=>date('Y-m-d H:i:s',time()+86400),
            ]);

            $givePointNums = config('param.test_vip_give_point',100);
            $insert = [
                'user_sn'=>$user['user_sn'],
                'nums'=>$givePointNums,
                'type'=>3,
                'way'=>1,
                'sourceid'=>$receiveId,
                'source_table'=>ReceiveVipByMobileModel::getInstance()->getTable(),
                'expired_time'=>date('Y-m-d H:i:s',time()+86400),
            ];
            UserPointFlowModel::getInstance()->insertGetId($insert);
            Db::connection('mysql')->commit();
            return [$receiveId];
        }catch (\Throwable $e){
            Db::connection('mysql')->rollBack();
            throw $e;
        }
    }

    public function userConsumePoint($param)
    {
        if(!empty($param['tool_id'])){
            $first = ToolManageModel::getInstance()->find($param['tool_id']);
            if($first==false){
                throw new IndexException(4020,'工具不存在');
            }
            if($first['charge_type']=='3'){
                $ponintParam = [
                    'source_table'=>$first['name'],
                    'sourceid'=>$param['tool_id'],
                ];
                $this->insertConsumePoint($first['point_nums'],$ponintParam,4);
            }

            return true;
        }elseif(!empty($param['consume_point'])){
            $ponintParam = [
                'source_table'=>$param['consume_remark']??'其他',
                'sourceid'=>0,
            ];
            $this->insertConsumePoint($param['consume_point'],$ponintParam,5);

            return true;
        }
    }

    public function getUserExampleList($param)
    {
        $field = ['id',Db::Raw('example_main_image image'),'video_url'];
        $where = [
            'is_example'=>1,
            'isdel'=>0,
        ];
        if(!empty($param['novel_sn'])){
            $where['novel_sn'] = $param['novel_sn'];
        }

        $data = UserPromotionPlanModel::getInstance()->getWhere($where)->orderBy('created_at','desc')->get($field)->toArray();
        if(empty($data) && !empty($param['novel_sn'])){
            $novel = NovelSpreadModel::getInstance()->getWhere(['novel_sn'=>$param['novel_sn']])->first();
            $where = [];
            !empty($novel['spread_type']) && $where['position'] = 'example_window_'.$novel['spread_type'];
            if($where){
                $where['status'] = 1;
                $where['isdel'] = 0;
                $data = VideoTutorialModel::getInstance()->getWhere($where)->get(['id','image','video_url'])->toArray();
            }
        }

        return $data;
    }

    // 测试登录方法
    public function testLogin($uuid)
    {
        Log::info('uuid为:' . $uuid, ['tset' => 'testest']);
        $user = UserModel::getInstance()->getWhere(['uuid'=>$uuid,['mobile','=',''],'isdel'=>0])->first();
        $user = $this->loginData($user);
        return $user;
    }

    // 测试加密方法
    public function testSign($param)
    {
        $platform = request()->header('platform');
        $headerData = [
            'uuid'=>request()->header('uuid'),
            'version'=>request()->header('version'),
            'platform'=>request()->header('platform'),
            'token'=>request()->header('token','empty'),
            'device'=>request()->header('device','empty'),
            'voucher'=>request()->header('voucher'),
            'time'=>request()->header('time'),
            'random'=>request()->header('random'),
        ];

        if($headerData['token']=='empty'){
            unset($headerData['token']);
        }
        if($headerData['device']=='empty'){
            unset($headerData['device']);
        }

        foreach($headerData as $key=>$val){
            if(in_array($key,['time','random','uuid','platform']) && $val==false){
                throw new IndexException(1008,'请求参数错误');
            }
            if($key=='voucher' && $val==false){
                throw new IndexException(1008,'请求签名验证错误');
            }
        }

        $paramData = request()->all();

        // 获取账号配置
        $signConfig = config('param.sign');

        // 缺少身份值
        if (!isset($signConfig[strtoupper($platform)])) {
            throw new IndexException(1008,'请求错误,缺少身份验证');
        }

        $signConfig = config('param.sign');
        $encryKey = $signConfig[strtoupper($platform)];

        // 生成签名
        // 不需要参与签名的键值
        if(strtoupper($headerData['platform'])=='ADMIN'){
            $noSignKey = ['voucher','file', 's', 'page', 'limit','field','order','tailor','__file__'];
        }else{
            $noSignKey = ['voucher','file'];
        }

        $str = '';
        ksort($headerData);
        foreach ($headerData as $key => $val) {
            if (in_array($key, $noSignKey)) {
                continue;
            }

            $val = htmlspecialchars_decode($val);
            if ($val === '' || is_null($val)) {
                $str .= $key.'=false{{ANYUE}}';
            }else{
                $str .= $key.'='.$val.'{{ANYUE}}';
            }
        }

        $string1 =  md5(md5($str).md5($encryKey));;

        $str = '';
        ksort($paramData);
        foreach ($paramData as $key => $val) {
            if (in_array($key, $noSignKey)) {
                continue;
            }

            if(is_array($val)){
                $isString = false;
                foreach($val as $k=>$v){
                    if(is_string($k)){
                        $isString = true;
                    }
                }
                if($isString){
                    $str .= $key.'=object{{ANYUE}}';
                }else{
                    $str .= $key.'=array{{ANYUE}}';
                }
            }else{
                if ($val === '' || is_null($val)) {
                    $str .= $key.'=false{{ANYUE}}';
                }else{
                    $str .= $key.'='.$val.'{{ANYUE}}';
                }
            }
        }

        $string2 =  md5(md5($str).md5($encryKey));
        return  $string1.$string2;

    }
}